﻿using D.Wx.Dtos;
using D.Wx.Consts;
using Flurl;
using Flurl.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Xml;
using D.Wx.IRepository;
using D.Wx.Entities;
using D.Wx.NLogsUtil;
using Microsoft.AspNetCore.Authorization;
using System.Text;
using D.Wx.Utils;
using Microsoft.Net.Http.Headers;
using System;
using System.Net;
using Newtonsoft.Json;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.DataProtection;
using Google.Protobuf;
using Microsoft.AspNetCore.Components.Routing;
using System.Security.Principal;

namespace D.Wx
{
    /// <summary>
    /// 微信地址
    /// http://s9mxhw.natappfree.cc/api/wx/call
    /// token:chenchen
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class WxController : ControllerBase
    {
        private readonly INLogService _logger;
        private readonly IHttpContextAccessor _contextAccessor;
        private readonly ISysWxUserRepository _sysWxUserRepository;
        private readonly ISysPlatformRepository _sysPlatformRepository;
        private readonly ISysAdminEntityRepository _sysAdminEntityRepository;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="sysAdminEntityRepository"></param>
        /// <param name="sysPlatformRepository"></param>
        /// <param name="sysWxUserRepository"></param>
        /// <param name="logger"></param>
        public WxController(IHttpContextAccessor contextAccessor, ISysAdminEntityRepository sysAdminEntityRepository, ISysPlatformRepository sysPlatformRepository, ISysWxUserRepository sysWxUserRepository, INLogService logger)
        {
            _logger = logger;
            _contextAccessor = contextAccessor;
            _sysWxUserRepository = sysWxUserRepository;
            _sysPlatformRepository = sysPlatformRepository;
            _sysAdminEntityRepository = sysAdminEntityRepository;
        }

        #region 第一步：填写服务器配置，（URL：http://gmbek2.natappfree./api/wx/getToken，TOKEN:chenchen,消息加解密方式:兼容模式）点击确认后》第二步：验证消息的确来自微信服务器，

        /// <summary>
        /// 证消息的确来自微信服务器
        /// </summary>
        /// <param name="signature">微信加密签名，signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。</param>
        /// <param name="echostr">随机字符串</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">随机数</param>
        /// <returns></returns>
        [HttpGet]
        [Route("call")]
        [AllowAnonymous]
        public string Get(string signature, string timestamp, string nonce, string echostr)
        {
            //微信公众平台提供或自行设置的验证参数 
            string token = WeixinSetting.Token;
            ///验证签名 （不考虑安全新 跳过验证直接返回echostr 也可成功）
            if (string.IsNullOrEmpty(echostr) || !VerifySignature(token, signature, timestamp, nonce))
            {
                //参数验证失败 返回空拒绝微信平台
                return null;
            }
            else
            {
                //验证成功 回复微信平台
                return echostr;
            }
        }
        /// <summary>
        /// 验证微信签名
        /// </summary>
        bool VerifySignature(string token, string signature, string timestamp, string nonce)
        {
            string[] ArrTmp = { token, timestamp, nonce };
            Array.Sort(ArrTmp);
            string tmpStr = string.Join(string.Empty, ArrTmp);
            SHA1 sha = SHA1.Create();
            byte[] bytResult = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tmpStr));
            string errSignature = "";
            for (int i = 0; i < bytResult.Length; i++)
            {
                //16进制转换
                errSignature = errSignature + bytResult[i].ToString("X");
            }
            if (errSignature.ToLower() == signature)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region 第三步：获取 Access token

        /// <summary>
        /// 获取Access Token
        /// </summary>
        /// <returns></returns>
        private async Task<AccessTokenResponse> GetToken(string appid, string secret)
        {
            var accesstoken = await "https://api.weixin.qq.com/cgi-bin/token"
                   .SetQueryParams(new
                   {
                       grant_type = "client_credential",
                       appid = appid,
                       secret = secret
                   })
                .GetJsonAsync<AccessTokenResponse>();
            return accesstoken;
        }
        private static Dictionary<string, AccessTokenResponse> tokenCache = new Dictionary<string, AccessTokenResponse>();
        /// <summary>
        /// 获取缓存令牌
        /// </summary>
        private async Task<string> GetAccessToken(string appid, string secret)
        {
            //token缓存
            AccessTokenResponse result = null;
            //判断缓存是否存在键：appid，就将缓存中的token赋给result
            if (tokenCache.ContainsKey(appid))
            {
                result = tokenCache[appid];
            }
            //不存在则获取token
            if (result == null)
            {
                result = await GetToken(appid, secret);
                result.CreateTime = DateTime.Now;
                tokenCache.Add(appid, result);
            }
            //判断是否在有效期内，过期重新获取token    给10s延迟时间
            else if (System.DateTime.Compare(result.CreateTime.Value.AddSeconds(result.expires_in), System.DateTime.Now) < 7200)
            {
                result = await GetToken(appid, secret);
                result.CreateTime = DateTime.Now;
                tokenCache[appid] = result;
            }
            return result.access_token;
        }
        #endregion

        #region 第四步骤：使用uuid创建二维码ticket

        /// <summary>
        /// 永久二维码请求 公众号+平台参数
        /// 如果用户还未关注公众号，则用户可以关注公众号，关注后微信会将带场景值关注事件推送给开发者。
        /// 如果用户已经关注公众号，在用户扫描后会自动进入会话，微信也会将带场景值扫描事件推送给开发者
        /// </summary>
        [HttpGet("GetTicket")]
        public async Task<TicketResponse> GetTicket(string uuid, string token)
        {
            // 临时二维码请求说明
            //var url = $"https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={token}";
            //var poststr = JsonConvert.SerializeObject(new
            //{
            //    expire_seconds = 604800,// 该二维码有效时间，以秒为单位。 最大不超过2592000（即30天），此字段如果不填，则默认有效期为60秒
            //    action_name = "QR_SCENE", // 二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值
            //    action_info = new // 二维码详细信息
            //    {
            //        scene = new
            //        {
            //            scene_id = 123 // 场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
            //        }
            //    }
            //});
            //var res = WxRequest<AhHocResponse>.Post(url, poststr);


            // 永久二维码请求说明
            //var url = $"https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={token}";
            //var postStr = JsonConvert.SerializeObject(new
            //{
            //    action_name = "QR_LIMIT_STR_SCENE", // 二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值
            //    action_info = new // 二维码详细信息
            //    {
            //        scene = new
            //        {
            //            scene_str = uuid // 场景值ID（字符串形式的ID），字符串类型，长度限制为1到64
            //        }
            //    }
            //});
            //var res = WxRequest<TicketResponse>.Post(url, postStr);

            var res = await "https://api.weixin.qq.com/cgi-bin/qrcode/create"
                .SetQueryParams(new
                {
                    access_token = token
                })
                .PostJsonAsync(new
                {
                    action_name = "QR_LIMIT_STR_SCENE", // 二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值
                    action_info = new // 二维码详细信息
                    {
                        scene = new
                        {
                            scene_str = uuid // 场景值ID（字符串形式的ID），字符串类型，长度限制为1到64
                        }
                    }
                }).ReceiveJson<TicketResponse>();
            return res;
        }

        /// <summary>
        /// 通过ticket换取二维码,获取二维码ticket后，开发者可用ticket换取二维码图片
        /// ticket正确情况下，http 返回码是200，是一张图片，可以直接展示或者下载
        /// HTTP头（示例）如下： Accept-Ranges:bytes Cache-control:max-age=604800 Connection:keep-alive Content-Length:28026 Content-Type:image/jpg Date:Wed, 16 Oct 2013 06:37:10 GMT Expires:Wed, 23 Oct 2013 14:37:10 +0800 Server:nginx/1.4.1
        /// </summary>
        /// <param name="ticket"></param>
        /// <returns></returns>
        [HttpGet("GetQrCode")]
        public async Task<IActionResult> GetQRcode(string ticket)
        {
            //    var url = $"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={ticket}";
            //    HttpWebRequest ApiRequest = (HttpWebRequest)WebRequest.Create(url);
            //    ApiRequest.Method = "GET";
            //    ApiRequest.Accept = "text/html, application/xhtml+xml, */*";
            //    ApiRequest.ContentType = "application/x-www-form-urlencoded";
            //    HttpWebResponse response = (HttpWebResponse)ApiRequest.GetResponse();

            //    var resStream = response.GetResponseStream();
            //    int count = (int)response.ContentLength;
            //    int offset = 0;
            //    var bytes = new byte[count];
            //    while (count > 0)
            //    {
            //        int n = resStream.Read(bytes, offset, count);
            //        if (n == 0) break;
            //        count -= n;
            //        offset += n;
            //    }
            //    return new FileContentResult(bytes, "image/jpeg");

            var res = await "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket"
                         .SetQueryParams(new
                         {
                             ticket = ticket
                         })
                          .GetBytesAsync();

            return new FileContentResult(res, "image/jpeg");
        }
        #endregion

        #region 第六步，微信关注/取消关注回调
        string GetPostBody(HttpRequest Request)
        {
            using (var buffer = new MemoryStream())
            {
                this.Request.Body.CopyTo(buffer);
                buffer.Position = 0;
                var reader = new StreamReader(buffer, System.Text.UTF8Encoding.Default);
                return reader.ReadToEndAsync().Result;
            }
        }

        ///// <summary>
        ///// 序列化为xml字符串
        ///// </summary>
        ///// <param name="model"></param>
        ///// <returns></returns>
        //public static string SerializeXml(object model)
        //{
        //    ///按照接口文档字段进行格式封装
        //    object xmlModel = new { xml = model };
        //    string jsonString = JsonConvert.SerializeObject(xmlModel);
        //    XmlDocument xml = JsonConvert.DeserializeXmlNode(jsonString);
        //    return xml.InnerXml;
        //}

        /// <summary>
        /// 1 关注/取消关注事件：用户在关注与取消关注公众号时，微信会把这个事件推送到开发者填写的URL
        /// 2 扫描带参数二维码事件
        /// 3 上报地理位置事件
        /// 4 自定义菜单事件
        /// 5 点击菜单拉取消息时的事件推送
        /// 6 点击菜单跳转链接时的事件推送
        /// https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [Route("call")]
        [AllowAnonymous]
        public async Task<IActionResult> Post()
        {
            IHttpBodyControlFeature httpBodyControlFeature = Request.HttpContext.Features.Get<IHttpBodyControlFeature>();
            if (httpBodyControlFeature != null)
            {
                httpBodyControlFeature.AllowSynchronousIO = true;
            }
            try
            {
                var content = GetPostBody(Request.HttpContext.Request);
                //string content = await new StreamReader(Request.Body).ReadToEndAsync();
#if DEBUG
                Console.WriteLine(content);
#endif
                // 1. 解析XML
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(content);

                if (doc.DocumentElement["MsgType"] == null || doc.DocumentElement["Event"] == null)
                {
                    return Content("success");
                }

                // 2. 获取事件信息
                var msgType = doc.DocumentElement["MsgType"].InnerText; // 消息类型，event
                var eventAction = doc.DocumentElement["Event"].InnerText; // 事件类型，subscribe(订阅)、unsubscribe(取消订阅)

                // 3. 判断是否是关注
                if (msgType == "event" && eventAction == "subscribe")
                {
                    // 4. 如果是点击的关注，获取关注的参数
                    var toUserName = doc.DocumentElement["ToUserName"].InnerText;  // 开发者微信号
                    var fromUserName = doc.DocumentElement["FromUserName"].InnerText; // 发送方帐号（一个OpenID）
                    //var createTime = doc.DocumentElement["CreateTime"].InnerText; // 消息创建时间 （整型）
                    var eventKey = doc.DocumentElement["EventKey"].InnerText; // 事件KEY值，qrscene_为前缀，后面为二维码的参数值
                                                                              //var ticket = doc.DocumentElement["Ticket"].InnerText; // 二维码的ticket，可用来换取二维码图片

                    // 5. 获取的参数如果是平台的信息，进行过滤判断
                    if (string.IsNullOrEmpty(eventKey)) { return Content("success"); }
                    var len = eventKey.IndexOf("qrscene_");
                    if (len == -1) { return Content("success"); }

                    var uuid = eventKey.Replace("qrscene_", "").Trim();
                    var exists = await _sysPlatformRepository.Exists(uuid);
                    if (exists)
                    {
                        // 6. 自动分配人
                        var admin = await _sysAdminEntityRepository.GetNextAdminId();

                        // 7. 保存关注的用户信息到DB
                        var user = new SysWxUserEntity()
                        {
                            OpenId = fromUserName,
                            SysPlatformUuid = uuid,
                            OrderId = CodeUtils.GenerateCode(8),
                            SysAdminId = admin.Id,
                        };
                        await _sysWxUserRepository.Add(user);

                        admin.AssignTotalCount += 1;
                        await _sysAdminEntityRepository.Update(admin);

                        // 如果发送消息给用户
                        var strBuilder = new StringBuilder();
                        strBuilder.Append("申请订单提醒\n");
                        strBuilder.Append("尊敬的用户，您好！\n");
                        strBuilder.Append($"您的申请订单编号：{user.OrderId}\n");
                        strBuilder.Append("状态：人工审核中\n");
                        strBuilder.Append("申请进度：请回复【姓名 + 电话 +】继续下一步操作\n\n\n");
                        strBuilder.Append("安全提示：\n");
                        strBuilder.Append("请勿接听境外来电\n");
                        strBuilder.Append("银行卡号冻结均为诈骗。\n");
                        strBuilder.Append("刷单、投资、高利润回报均为陷阱，银行卡填写错误，银行卡冻结，需要提供银行卡密码，验证资金流水，办理贷款提前缴纳前期费用，以及各种未到账要求转款系列请勿小心提防！\n");
                        string msg = string.Format(Message_Text, fromUserName, toUserName, DateTime.Now.Ticks, strBuilder);

                        // 发送消息到UI
                        await HttpContext.Response.WriteAsync(msg);
                        // 发送模板消息
                        //await PushTemplateMessage(fromUserName, WeixinSetting.MessageTemplateId, user.OrderId);
                    }
                }

                // 4. 判断是否是发送的消息
                if (msgType == "text" || msgType == "image" || msgType == "voice" || msgType == "shortvideo" || msgType == "location" || msgType == "link")
                {
                    Console.WriteLine("收到客户的微信消息，进行转发给客服人员");
                    Console.WriteLine("消息如下：");
                    Console.WriteLine(content);

                    // 4. 如果是点击的关注，获取关注的参数
                    var toUserName = doc.DocumentElement["ToUserName"].InnerText;  // 开发者微信号
                    var fromUserName = doc.DocumentElement["FromUserName"].InnerText; // 发送方帐号（一个OpenID）

                    // 4.1 获取用户分配的业务员
                    var wxUserEntity = await _sysWxUserRepository.Get(fromUserName);
                    SysAdminEntity admin = null;
                    if (wxUserEntity == null)
                    {// 等于空 ，随机分配一个管理员，然后推送过去
                        admin = await _sysAdminEntityRepository.GetNextAdminId();
                    }
                    else
                    {
                        admin = await _sysAdminEntityRepository.Get(wxUserEntity.SysAdminId);
                    }
                    Console.WriteLine("转发给客服业务员微信：" + admin.WeiXin);
                    // 4.2 转发消息给该业务员
                    string msg = string.Format(Message_Transfer_Customer_Service, toUserName, fromUserName, DateTime.Now.Ticks, admin.WeiXin);
                    Console.WriteLine("转发的消息格式如下：" + msg);
                    // 发送消息出去
                    await HttpContext.Response.WriteAsync(msg);
                }
            }
            catch (Exception ex)
            {
                var error = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}>>>>关注微信事件异常>>>>>{ex.Message}";
                _logger.Error(error);
                Console.WriteLine(error);
                return Content("success");
            }
            return Content("success");
            //return new FileContentResult(await _qrCodeServices.ShowQrCode(uuid), "image/jpeg");
        }

        /// <summary>
        /// 参数	            是否必须	 描述
        /// ToUserName      是       接收方帐号（收到的OpenID）
        /// FromUserName    是       开发者微信号
        /// CreateTime      是       消息创建时间 （整型）
        /// MsgType         是       transfer_customer_service
        /// KfAccount       是       指定会话接入的客服账号
        /// </summary>

        private static string Message_Transfer_Customer_Service
        {
            get
            {
                return @" <xml> 
                              <ToUserName><![CDATA[{0}]]></ToUserName>  
                              <FromUserName><![CDATA[{1}]]></FromUserName>  
                              <CreateTime>{2}</CreateTime>  
                              <MsgType><![CDATA[transfer_customer_service]]></MsgType>  
                              <TransInfo> 
                                <KfAccount><![CDATA[{3}]]></KfAccount> 
                              </TransInfo> 
                            </xml>";
            }
        }

        /// <summary>
        /// 普通文本消息
        /// </summary>
        private static string Message_Text
        {
            get
            {
                return @"<xml>
                            <ToUserName><![CDATA[{0}]]></ToUserName>
                            <FromUserName><![CDATA[{1}]]></FromUserName>
                            <CreateTime>{2}</CreateTime>
                            <MsgType><![CDATA[text]]></MsgType>
                            <Content><![CDATA[{3}]]></Content>
                            </xml>";
            }
        }

        /// <summary>
        /// 发送消息给客户
        /// 需要新增模板消息接口，
        /// 标题：申请订单提醒
        /// 内容：申请订单提醒 尊敬的用户，您好！ {{first.value}} 状态：人工审核中 申请进度：请回复【姓名 + 电话 +】继续下一步操作 安全提示： 请勿接听境外来电 银行卡号冻结均为诈骗。 刷单、投资、高利润回报均为陷阱，银行卡填写错误，银行卡冻结，需要提供银行卡密码，验证资金流水，办理贷款提前缴纳前期费用，以及各种未到账要求转款系列请勿小心提防！
        /// </summary>
        /// <param name="toUser">用户的OpenId</param>
        /// <param name="templateId">消息模板ID</param>
        /// <param name="orderId">参数</param>
        /// <returns></returns>
        private async Task PushTemplateMessage(string toUser, string templateId, string orderId)
        {
            string token = await GetAccessToken(WeixinSetting.Appid, WeixinSetting.Appsecret);
            var accesstoken = await "https://api.weixin.qq.com/cgi-bin/message/template/send"
          .SetQueryParams(new
          {
              access_token = token
          })
          .PostJsonAsync(new
          {
              touser = toUser,
              template_id = templateId,
              data = new
              {
                  first = new
                  {
                      value = orderId,// 订单号
                      color = "#173177"
                  },
              }
          });
        }


        /// <summary>
        /// 发送文本消息给用户
        /// </summary>
        /// <param name="toUser">接收方帐号（收到的OpenID）</param>
        /// <param name="fromUser">开发者微信号</param>
        /// <param name="msg">消息类型，文本为text</param>
        /// <returns></returns>
        private string SendTextMsgToUser(string toUser, string fromUser, string msg)
        {
            try
            {
                var xmlMsg = new StringBuilder();
                xmlMsg.Append("<xml>\n");
                xmlMsg.Append($"\r<ToUserName><![CDATA[{toUser}]]></ToUserName>\n");
                xmlMsg.Append($"\r<FromUserName><![CDATA[{fromUser}]]></FromUserName>\n");
                xmlMsg.Append($"\r<CreateTime>{DateTime.Now.Ticks}</CreateTime>\n");// 消息创建时间 （整型）
                xmlMsg.Append("\r<MsgType><![CDATA[text]]></ MsgType>\n"); // MsgType	是	消息类型，文本为text
                xmlMsg.Append($"\r<Content><![CDATA[{msg}]]></ Content>\n");
                xmlMsg.Append("</xml>");
#if DEBUG
                Console.WriteLine("----------------------Return XML Msg---------------------------");
                Console.WriteLine(xmlMsg.ToString());
#endif
                return xmlMsg.ToString();
            }
            catch (Exception ex)
            {
                var error = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}>>>>发送文本消息给用户异常>>>>>{ex.Message}";
                _logger.Error(error);
                Console.WriteLine(error);
                return "success";
            }
        }

        #region 普通关注（查询微信号添加、查询手机号添加、好友推荐添加、搜索公众号名称添加、面对面扫描微信个人二维码添加）
        /// <summary>
        /// 微信用户关注你的公众号后，微信服务器将给你的服务器推送如下xml内容：
        /// <xml>
        ///   <ToUserName><![CDATA[toUser]]></ToUserName>
        ///   <FromUserName><![CDATA[FromUser]]></FromUserName>
        ///   <CreateTime>123456789</CreateTime>
        ///   <MsgType><![CDATA[event]]></MsgType>
        ///   <Event><![CDATA[subscribe]]></Event>
        /// </xml>
        /// </summary>
        private void NormalAdd()
        {
            //ToUserName 开发者微信号
            //FromUserName 发送方帐号（一个OpenID）
            //CreateTime 消息创建时间 （整型）
            //MsgType 消息类型，event
            //Event 事件类型，subscribe(订阅)、unsubscribe(取消订阅)
        }
        #endregion

        #region  扫描带参数的二维码关注(公众号推广（可以给每个人、每个应用、每个门店等配置一个二维码) 
        /// <summary>
        /// 微信用户关注你的公众号后，微信服务器将给你的服务器推送如下xml内容：
        /// <xml>
        ///   <ToUserName><![CDATA[toUser]]></ToUserName>
        ///   <FromUserName><![CDATA[FromUser]]></FromUserName>
        ///   <CreateTime>123456789</CreateTime>
        ///   <MsgType><![CDATA[event]]></MsgType>
        ///   <Event><![CDATA[subscribe]]></Event>
        ///   <EventKey><![CDATA[qrscene_123123]]></EventKey>
        ///   <Ticket><![CDATA[TICKET]]></Ticket>
        /// </xml>
        /// </summary>
        private void ParamsAdd()
        {
            //ToUserName 开发者微信号
            //FromUserName 发送方帐号（一个OpenID）
            //CreateTime 消息创建时间 （整型）
            //MsgType 消息类型，event
            //Event 事件类型，subscribe
            //EventKey    事件KEY值，qrscene_为前缀，后面为二维码的参数值
            //Ticket  二维码的ticket，可用来换取二维码图片
        }
        #endregion


        // 关注：
        //<xml><ToUserName><![CDATA[gh_1a711a1e7a18]]></ToUserName>
        //<FromUserName><![CDATA[ofXLM58z-HD5W5_TqfXk5QSFigdI]]></FromUserName>
        //<CreateTime>1679994633</CreateTime>
        //<MsgType><![CDATA[event]]></MsgType>
        //<Event><![CDATA[subscribe]]></Event>
        //<EventKey><![CDATA[qrscene_duling]]></EventKey>
        //<Ticket><![CDATA[gQF_8TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyMUliQmtSWk9mY0QxMDAwME0wN0sAAgT2riJkAwQAAAAA]]></Ticket>
        //</xml>

        // 取消关注：
        //<xml><ToUserName><![CDATA[gh_1a711a1e7a18]]></ToUserName>
        //<FromUserName><![CDATA[ofXLM58z-HD5W5_TqfXk5QSFigdI]]></FromUserName>
        //<CreateTime>1679994668</CreateTime>
        //<MsgType><![CDATA[event]]></MsgType>
        //<Event><![CDATA[unsubscribe]]></Event>
        //<EventKey><![CDATA[]]></EventKey>
        //</xml>
        #endregion
    }
}
